from datetime import datetime
import logging
from core.entities.account_role import AccountWrap
from extensions.ext_database import db
from models.robot.robots import AgentType,Robots
from models.robot.rob_version import FVerTaskStatus,RobHardwareVersion,RobFirewareVersion,FVerTask,FVerTaskResult
from services.errors.robots import RobotNotExistError, RobotParameterError
from services.errors.versiions import FVerTaskNotExistError, FVerTaskResultNotExistError, FirewareParamsError, FirewareVersionNotExistError
from flask_babel import _

from services.log_service import LogAction, LogService
class FVerService:
    @staticmethod
    def get_fver_list(tenant_id:str,ver:str|None,agent_type:AgentType|None,is_new:bool|None,page:int=-1,page_size:int=-1)->dict:
        try:
            query = RobFirewareVersion.query.filter(RobFirewareVersion.tenant_id==tenant_id)
            has_more = False
            if ver:
                query = query.filter(RobFirewareVersion.ver.ilike(f"%{ver}%"))
            if agent_type:
                query = query.filter(RobFirewareVersion.agent_type==agent_type.value)
            if is_new:
                query = query.filter(RobFirewareVersion.is_new==is_new)
            total = query.count()
            if page and page>0 and page_size and page_size>0:
                query = query.offset((page-1)*page_size).limit(page_size)
                has_more = total > page_size * page
            data = query.all()
            return {
                "total":total,
                "has_more":has_more,
                "data":data
            }
        except Exception as e:
            logging.error(e,exc_info=True)
        return {
            "total":0,
            "has_more":False,
            "data":[]
        }
    
    @staticmethod
    def create_fver(account:AccountWrap,args:dict,ip_addr:str)->RobFirewareVersion:
        if not account or not isinstance(account,AccountWrap):
            raise NotLoginError()
        ver  = args.get("ver",None)
        md5 = args.get("md5",None)
        file_id = args.get("file_id",None)
        file_url = args.get("file_url",None)
        agent_type = args.get("agent_type",None)
        is_new = args.get("is_new",None)
        descrip = args.get("descrip",None)
        if not ver or not agent_type or is_new is None:
            raise FirewareParamsError(_("Fireware version, agent type and is_new cannot be empty."))
        if not file_id and not file_url:
            raise FirewareParamsError(_("File id or file url at least one cannot be empty."))
        if agent_type not in AgentType.choices():
            raise FirewareParamsError(_("Agent type is not valid."))
        if is_new is not None and is_new:
            exit_ver = db.session.query(RobFirewareVersion).filter(
                RobFirewareVersion.tenant_id == account.current_tenant_id,
                RobFirewareVersion.agent_type == agent_type,
                RobFirewareVersion.is_new==True).first()
            if exit_ver:
                raise FirewareParamsError(_("There is already a new version of this agent type."))
            
        exit_ver = db.session.query(RobFirewareVersion).filter(
            RobFirewareVersion.tenant_id == account.current_tenant_id,
            RobFirewareVersion.agent_type == agent_type,
            RobFirewareVersion.ver == ver
        ).first()
        if exit_ver:
            raise FirewareParamsError(_("Fireware version already exists. Version:{ver}").format(ver=ver))

        try:
            
            fver = RobFirewareVersion(
                tenant_id = account.current_tenant_id, #type: ignore
                ver = ver, #type: ignore
                md5 = md5, #type: ignore
                agent_type = agent_type, #type: ignore
                file_id = file_id, #type: ignore
                file_url = file_url, #type: ignore
                is_new = is_new, #type: ignore
                descrip = descrip,# type: ignore
                created_by = account.id,# type: ignore
            )
            db.session.add(fver)
            db.session.flush()
            LogService.log(
                tenant_id=account.current_tenant_id,#type: ignore
                account_id = account.id, #type: ignore
                action = LogAction.CREATE_FW_VER, 
                content = f"User {account.name} create fireware_version:{fver.id}:{fver.ver}",
                created_ip = ip_addr,
                submit= False,
            )
            db.session.commit()
            return fver
        except Exception as e:
            db.session.rollback()
            raise e
        
    @staticmethod
    def update_fver(account:AccountWrap,args:dict,ip_addr:str)->RobFirewareVersion:
        if not account or not isinstance(account,AccountWrap):
            raise NotLoginError()
        fver_id = args.get("id",None)
        if not fver_id:
            raise FirewareParamsError(_("Fireware version id is required"))
        fver = db.session.get(RobFirewareVersion,fver_id)
        if not fver:
            raise FirewareVersionNotExistError(fver_id)
        ver  = args.get("ver",None)
        md5 = args.get("md5",None)
        file_id = args.get("file_id",None)
        file_url = args.get("file_url",None)
        agent_type = args.get("agent_type",None)
        is_new = args.get("is_new",None)
        descrip = args.get("descrip",None)
        if not ver or not agent_type or is_new is None:
            raise FirewareParamsError(_("Fireware version, agent type and is_new cannot be empty."))
        if not file_id and not file_url:
            raise FirewareParamsError(_("File id or file url at least one cannot be empty."))
        if agent_type not in AgentType.choices():
            raise FirewareParamsError(_("Agent type is not valid."))
        if is_new is not None:
            if is_new:
                exit_ver = db.session.query(RobFirewareVersion).filter(
                    RobFirewareVersion.id != fver_id,
                    RobFirewareVersion.tenant_id == account.current_tenant_id,
                    RobFirewareVersion.agent_type == agent_type,
                    RobFirewareVersion.is_new==True).first()
                if exit_ver:
                    raise FirewareParamsError(_("There is already a new version of this agent type."))
            fver.is_new = is_new
            
        exit_ver = db.session.query(RobFirewareVersion).filter(
            RobFirewareVersion.id != fver_id,
            RobFirewareVersion.tenant_id == account.current_tenant_id,
            RobFirewareVersion.agent_type == agent_type,
            RobFirewareVersion.ver == ver
        ).first()
        if exit_ver:
            raise FirewareParamsError(_("Fireware version already exists. Version:{ver}").format(ver=ver))

        try:
            fver.ver = ver
            fver.md5 = md5
            fver.file_id = file_id
            fver.file_url = file_url
            fver.agent_type = agent_type
            fver.descrip = descrip
            db.session.flush()
            LogService.log(
                tenant_id=account.current_tenant_id,#type: ignore
                account_id = account.id, #type: ignore
                action = LogAction.UPDATE_FW_VER, 
                content = f"User {account.name} UPDATE fireware_version:{fver.id}:{fver.ver}",
                created_ip = ip_addr,
                submit= False,
            )
            db.session.commit()
            return fver
        except Exception as e:
            db.session.rollback()
            raise e
        

    @staticmethod    
    def delete_fver(account:AccountWrap,fver_id:str,ip_addr:str):
        try:
            tenant_id = account.current_tenant_id
            fver = db.session.get(RobFirewareVersion,fver_id)
            if not fver:
                raise FirewareVersionNotExistError(fver_id)
            if fver.tenant_id != tenant_id:
                raise FirewareParamsError(_("This fireware version does not belong to your account"))
            task = db.session.query(FVerTask).filter(FVerTask.fver_id==fver_id,FVerTask.is_deleted==False).first()
            if task:
                raise FirewareParamsError(_("The fireware version is in use,delete update task first"))
            LogService.log(
                tenant_id=account.current_tenant_id,#type: ignore
                account_id = account.id, #type: ignore
                action = LogAction.DEL_FW_VER, 
                content = f"User {account.name} delete fireware_version:{fver.id}:{fver.ver}",
                created_ip = ip_addr,
                submit= False,
            )
            db.session.delete(fver)
            tasks = db.session.query(FVerTask).filter(FVerTask.fver_id==fver_id).all()
            for task in tasks:
                db.session.query(FVerTaskResult).filter(FVerTaskResult.task_id==task.id).delete()
                db.session.delete(task)
            db.session.commit()
        except Exception as e:
            db.session.rollback()
            raise e
        
    @staticmethod
    def get_fver_tasks(args:dict)->list[FVerTask]:
        id = args.get("id",None)
        name = args.get("name",None)
        fver_id = args.get("fver_id",None)
        start = args.get("start",None)
        end = args.get("end",None)
        include_del = args.get("include_del",False)
        try:
            query = db.session.query(FVerTask)
            if not include_del:
                query = query.filter(FVerTask.is_deleted==False)
            if id:
                query = query.filter(FVerTask.id==id)
            else:
                if name:
                    query = query.filter(FVerTask.name.ilike(f"%{name}%"))
                if fver_id:
                    query = query.filter(FVerTask.fver_id==fver_id)
                if start:
                    query = query.filter(FVerTask.reservation_at>=start)
                if end:
                    query = query.filter(FVerTask.reservation_at<=end)
            data = query.all()
            return data
        except Exception as e:
            raise e
        
    @staticmethod
    def get_fver_task_by_id(task_id)->FVerTask|None:
        return db.session.get(FVerTask,task_id)

    
    @staticmethod
    def create_task(account:AccountWrap,fver_id:str,name:str,remark:str|None,robot_ids:list[str]|None,reservation_at:datetime,ip_addr)->FVerTask:
        try:
            fver = db.session.get(RobFirewareVersion,fver_id)
            if not fver:
                raise FirewareVersionNotExistError(fver_id)
            agent_type = fver.agent_type
            ver = fver.ver
            task = FVerTask(
                name = name, # type: ignore
                fver_id = fver_id, # type: ignore
                remark = remark, # type: ignore
                created_by = account.id, # type: ignore
                reservation_at = reservation_at # type: ignore
            )
            db.session.add(task)
            db.session.flush()
            task_id = str(task.id)
            robots = []
            if not robot_ids or len(robot_ids) == 0:
                robots = db.session.query(Robots).filter(Robots.agent_type == agent_type,Robots.f_version != ver).all()
            else:
                robots = db.session.query(Robots).filter(Robots.id.in_(robot_ids)).all()
            for robot in robots:
                task_result = FVerTaskResult(
                    task_id=task_id, # type: ignore
                    robot_id=robot.id, # type: ignore
                    new_ver=fver.ver, # type: ignore
                    status=FVerTaskStatus.PENDING.value,  # type: ignore
                    pushed_at = datetime.now(), # type:ignore
                    last_state_at=datetime.now(), # type: ignore
                )
                db.session.add(task_result)
            LogService.log(
                tenant_id=account.current_tenant_id,#type: ignore
                account_id = account.id, #type: ignore
                action = LogAction.CREATE_FWVER_TASK, 
                content = f"User {account.name} CREATE fireware_version TASK:{task.id}:{task.name}",
                created_ip = ip_addr,
                submit= False,
            )
            db.session.commit()
            return task
        except Exception as e:
            logging.error(e,stack_info=True)

    @staticmethod
    def delete_fver_task(account:AccountWrap,task_id:str,ip_addr:str)->int:
        try:
            task = db.session.get(FVerTask, task_id)
            if not task:
                raise FVerTaskNotExistError(task_id)
            stop_count = db.session.query(FVerTaskResult).filter(
                FVerTaskResult.task_id == task_id,
                FVerTaskResult.status == FVerTaskStatus.PENDING.value
            ).update({
                "status": FVerTaskStatus.STOP.value
            })
            task.updated_at = datetime.now()
            task.updated_by = account.id
            task.is_deleted = True
            LogService.log(
                tenant_id=account.current_tenant_id,
                account_id=account.id,
                action = LogAction.DELETE_FWVER_TASK,
                content=f"User {account.name} deleted fver task {task.id}:{task.name}",
                created_ip = ip_addr,
                submit= False,
            )
            db.session.commit()
            return stop_count
        except Exception as e:
            db.session.rollback()
            raise e

    @classmethod
    def update_fver_task_status(cls,task_id:str,robot_id,bluetooth_id,status):
        robot = cls.verify_robot_bluetooth(robot_id, bluetooth_id)
        task_result = db.session.get(FVerTaskResult,task_id)
        if not task_result:
            raise FVerTaskResultNotExistError(task_id)
        try:
            task_result.status = status
            if status == FVerTaskStatus.UPDATING:
                task_result.update_at = datetime.now()
            elif status == FVerTaskStatus.SUCCESS:
                task_result.updated_at = datetime.now()
                robot.f_version = task_result.new_ver
            # elif status == FVerTaskStatus.FAILED:
            task_result.last_state_at = datetime.now()
            db.session.commit()
        except Exception as e:
            logging.error(e)
            raise e
        
    @classmethod
    def check_robot_update_task(cls,robot_id,bluetooth_id)->dict:
        robot = cls.verify_robot_bluetooth(robot_id,bluetooth_id)
        task = db.session.query(FVerTaskResult).filter(
            FVerTaskResult.robot_id == robot_id,
            FVerTaskResult.new_ver != robot.f_version,
            ~FVerTaskResult.status.in_([FVerTaskStatus.STOP.value,FVerTaskStatus.SUCCESS.value])
        ).order_by(FVerTaskResult.pushed_at.desc()).first()
        if task:
            origin_task = db.session.get(FVerTask,task.task_id)
            if origin_task and not origin_task.is_deleted:
                fver = db.session.get(RobFirewareVersion,origin_task.fver_id)
                if fver:
                    return {
                        "has":True,
                        "task_id":task.id,
                        "status":task.status,
                        "new_ver":task.new_ver,
                        "url":fver.url,
                        "md5":fver.md5,
                        "pushed_at":task.pushed_at.timestamp() if task.pushed_at else 0
                    }
        return {
            "has":False
        }


    @staticmethod
    def verify_robot_bluetooth(robot_id, bluetooth_id)->Robots:
        robot = db.session.get(Robots,robot_id)
        if not robot:
            raise RobotNotExistError(robot_id)
        if robot.bluetooth_id != bluetooth_id:
            raise RobotParameterError(_("Robot id doesn't match boluetooth id"))
        return robot
        
    @staticmethod
    def get_fver_by_id(ver_id)->RobFirewareVersion|None:
        return db.session.get(RobFirewareVersion,ver_id)
    
    @staticmethod
    def get_fver_statistic_by_id(ver_id)->dict|None:
        fver = db.session.get(RobFirewareVersion,ver_id)
        result = {
            "all":0,
            "waitUpdate":0,
            "update":0,
            "success":0,
            "error":0
        }
        all_devs = 0
        waitUpdate = 0
        update_count = 0
        success = 0
        error = 0
        stoped = 0
        if fver:
            tasks = db.session.query(FVerTask).filter(FVerTask.fver_id==ver_id).all()
            for task in tasks:
                if task.is_deleted:
                    continue
                task_results = task.results
                for task_result in task_results:
                    all_devs +=1
                    match task_result.status.upper():
                        case FVerTaskStatus.PENDING:
                            waitUpdate+=1
                        case FVerTaskStatus.UPDATING:
                            update_count +=1
                        case FVerTaskStatus.SUCCESS:
                            success +=1
                        case FVerTaskStatus.FAILED:
                            error +=1
                        case FVerTaskStatus.STOP:
                            stoped +=1
        else:
            raise FirewareVersionNotExistError(ver_id)
        return {
            "all":all_devs,
            "waitUpdate":waitUpdate,
            "update":update_count,
            "success":success,
            "error":error,
            "stop":stoped
        }



    @staticmethod
    def get_fver_task_statistic_by_id(task_id)->dict|None:
        task = db.session.get(FVerTask,task_id)
        all_devs = 0
        waitUpdate = 0
        update_count = 0
        success = 0
        error = 0
        stoped = 0
        if task:
            task_results = task.results
            for task_result in task_results:
                all_devs +=1
                match task_result.status.upper():
                    case FVerTaskStatus.PENDING:
                        waitUpdate+=1
                    case FVerTaskStatus.UPDATING:
                        update_count +=1
                    case FVerTaskStatus.SUCCESS:
                        success +=1
                    case FVerTaskStatus.FAILED:
                        error +=1
                    case FVerTaskStatus.STOP:
                        stoped +=1
        else:
            raise FVerTaskNotExistError(task_id)
        return {
            "all":all_devs,
            "waitUpdate":waitUpdate,
            "update":update_count,
            "success":success,
            "error":error,
            "stop":stoped
        }
    
    @staticmethod
    def get_fver_task_results_by_id(task_id,status,robot_id,page,page_size)->dict:
        try:
            task = db.session.get(FVerTask,task_id)
            if not task:
                raise FVerTaskNotExistError(task_id)
            query = db.session.query(FVerTaskResult).filter(FVerTaskResult.task_id==task_id)
            if status:
                query = query.filter(FVerTaskResult.status==status)
            if robot_id:
                query = query.filter(FVerTaskResult.robot_id==robot_id)
            total = query.count()
            has_more = False
            if page and page >0 and page_size and page_size >0:
                query = query.offset((page-1)*page_size).limit(page_size)
                has_more = total > page*page_size
            data = query.all()
            return {
                "total":total,
                "has_more":has_more,
                "data":data
            }
        except Exception as e:
            logging.error(str(e),stack_info=True)
            raise e